/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package agen.util;

import agen.AGenApp;
import agen.cruce.Cruce;
import agen.cruce.MetodoCruceCombinado;
import agen.mutacion.Mutacion;
import agen.optimalidad.Aptitud;
import agen.optimalidad.Optimo;
import agen.seleccion.MetodoSeleccionCombinado;
import agen.seleccion.Seleccion;
import agen.util.exception.MetodoInexistente;
import agen.util.exception.PorcentajesInconsistentes;
import agen.util.graficos.ArmarDataSets;
import agen.util.graficos.XYChart;
import java.awt.RenderingHints.Key;
import java.util.Arrays;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jblas.DoubleMatrix;

/**
 *
 * @author jmfernandez
 */
public class CorrerThreaded extends Thread {
    int corteIteraciones = -1;          /*Cuando iteracion seaigual a
                                         * corteIteraciones se hace pausa*/
    DoubleMatrix matrizMulticriterio;
    int[][] ubicaciones;                /*posision, fila y columna que ocupa
                                         * cada elto del individuo en matrizMulticriterio*/
    int porcentajeSeleccion;            /*% de poblacionModificada compuesta por
                                         * seleccionn*/
    int porcentajeCruce;                /*% de poblacionModificada compuesta por
                                         * Cruce*/
    int porcentajeMutacion;             /*% de poblacionModificada compuesta por
                                         * Mutacion*/
    int porcentajeElitista;             /*% en seleccion realizado con S Elitista*/
    int porcentajeRanking;              /*% en seleccion realizado con S Ranking*/
    int porcentajeRuleta;               /*% en seleccion realizado con S Ruleta*/
    int porcentajeBinomial;             /*% en Cruce realizado con C Binomial*/
    int porcentajePunto;                /*% en Cruce realizado con C Punto*/
    int porcentajeMultiPunto;           /*% en Cruce realizado con C MultiPunto*/
    int[] multiPunto;                   /*puntos de corte en el individuo para
                                         * realizar el cruce con punto o multipunto*/
    int metodoMutacion;                 /*Metodo elegido para realizar la mutación,
                                         * ver clase Mutacion*/
    double maxPMutacion;                /*Cota superior de la p de mutación*/
    double MinPMutacion;                /*Cota inferior de la p de mutación*/
    byte[] poblacion;                   /*Contiene a todos los individuos en serie*/
    byte[] poblacionModificada;         /*Contiene la nueva generacion a medida
                                         *que se va formando, despues se asigna
                                          *a poblacion*/
    int iteracion = -1;                  /*Contador de generaciones*/
    int[] offsets = new int[3];
    public int[] getOffsets() {
        return offsets;
    }
    public int getIteracion() {
        return iteracion;
    }
    int[] aptitudesOrdenadas;           /*Al ordenar aptitudes[] en aptitudesOrdenadas[]
                                         * queda registro de la ubicacion original
                                         * de cada aptitud*/
    double[] aptitudes;                 /*Resultado de LamdaMax - OrdenMatriz*/
    int indiceAptitudOptima = -1;     /*Al cortar, almacena el indice del
                                        *individuo con la mejor aptitud*/
    boolean emitirLog = false;      /* si es true se escribe en un archivo valores
                                        relevantes al final de cada Generacio o
                                        corrida*/
    boolean correrBatch = false;    /*si es true, no termina la ejecucion con una sola
                                    corrida, sino despues de numCorridasBatch*/
    int numCorridasBatch = 100;     /*ver correrBatch*/

    public CorrerThreaded(DoubleMatrix matrizMulticriterio,
            int[][] ubicaciones, int porcentajeSeleccion, int porcentajeCruce,
            int porcentajeMutacion, int porcentajeElitista, int porcentajeRanking,
            int porcentajeRuleta, int porcentajeBinomial, int porcentajePunto,
            int porcentajeMultiPunto, int[] multiPunto, int metodoMutacion,
            double maxPMutacion, double MinPMutacion, byte[] poblacion,
            byte[] poblacionModificada, int cortarEnIteracion, boolean emitirLog,
            boolean correrBatch, int numCorridasBatch) {

        indiceAptitudOptima = -1;
        iteracion = -1;
        this.matrizMulticriterio = matrizMulticriterio;
        this.ubicaciones = ubicaciones;
        this.porcentajeSeleccion = porcentajeSeleccion;
        this.porcentajeCruce = porcentajeCruce;
        this.porcentajeMutacion = porcentajeMutacion;
        this.porcentajeElitista = porcentajeElitista;
        this.porcentajeRanking = porcentajeRanking;
        this.porcentajeRuleta = porcentajeRuleta;
        this.porcentajeBinomial = porcentajeBinomial;
        this.porcentajePunto = porcentajePunto;
        this.porcentajeMultiPunto = porcentajeMultiPunto;
        this.multiPunto = multiPunto;
        this.metodoMutacion = metodoMutacion;
        this.maxPMutacion = maxPMutacion;
        this.MinPMutacion = MinPMutacion;
        this.poblacion = poblacion;
        this.poblacionModificada = poblacionModificada;
        this.corteIteraciones = cortarEnIteracion;
        this.correrBatch = correrBatch;
        this.emitirLog = emitirLog;
        this.numCorridasBatch = numCorridasBatch;
    }

    @Override
    synchronized public void run() {
        double[] individuoOptimo = new double[ubicaciones.length];
        while (indiceAptitudOptima == -1) {
            indiceAptitudOptima = iterar();
             if (indiceAptitudOptima != -1) {
                 if (emitirLog){
                     escribirLog();

                 }
                AGenApp.corriendo = false;
                int indexEnPoblacion = indiceAptitudOptima * ubicaciones.length;
                for (int i = 0; i < ubicaciones.length; i++) {
                    individuoOptimo[i] = AGenApp.prioridades[poblacion[indexEnPoblacion + i]];
                }
                if (!correrBatch && numCorridasBatch == 1 ) {
                    String texto = "<html>Se ha detenido la ejecución debido a que se " +
                        "encontró un individuo optimo. " +
                        "Precione <b>\"Correr\"</b> nuevamente para ejecutar el " +
                        "algoritmo con los mismos parámetros. <br>" +
                        "O bien, cambie las configuraciones si lo desea.<br>" +
                        "Los elementos del individuo se corresponden con las incognitas" +
                        "sobre la diagonal Ppal. tomadas de izquierda a derecha y de " +
                        "arriba hacia abajo.</html>";
                    agen.dialogs.Aviso aviso = new agen.dialogs.Aviso(AGenApp.
                            getApplication().getMainFrame(),true,texto,"Optimo Encontrado");
                    aviso.setVisible(true);
                }
            } else {
                AGenApp.setPauseValues(Mutacion.calcularConvergenciaIndividuos(poblacion,
                    ubicaciones.length), aptitudes[0], iteracion, cr(),
                    Arrays.copyOfRange(poblacion, aptitudesOrdenadas[0] *ubicaciones.length,
                    aptitudesOrdenadas[0] *ubicaciones.length + ubicaciones.length ));
                XYChart.agregarAPanel(AGenApp.getInterfaz()
                    .getpanelgragicos(), ArmarDataSets.armarDataSetAptitudesResumen(
                    AGenApp.historialAptitudes, iteracion), "Historial" +
                    " de Aptitudes", true, "Generaciones", "Aptitudes");

                AGenApp.actualizarGraficoAptitudesXGen(iteracion);
                AGenApp.getInterfaz().getTxtNavegarGeneraciones().setText(
                        String.valueOf(iteracion));
                try {
                    wait();
                } catch (InterruptedException ex) {
                    Logger.getLogger(CorrerThreaded.class.getName()).log(Level.SEVERE, null, ex);
                }
//                
            }

        }
        if (!correrBatch && numCorridasBatch == 1 ) {
            XYChart.agregarAPanel(AGenApp.getInterfaz()
                    .getpanelgragicos(), ArmarDataSets.armarDataSetAptitudesResumen(
                    AGenApp.historialAptitudes, iteracion
                    - Optimo.generacionesCorteOptimo), "Historial" +
                    " de Aptitudes", true,"Generaciones", "Aptitudes");

            AGenApp.actualizarGraficoAptitudesXGen(iteracion
                        - Optimo.generacionesCorteOptimo);
            AGenApp.setPauseValues(Mutacion.calcularConvergenciaIndividuos(poblacion,
                    ubicaciones.length), aptitudes[0], iteracion, cr(),
                    Arrays.copyOfRange(poblacion, aptitudesOrdenadas[0] *ubicaciones.length,
                    aptitudesOrdenadas[0] *ubicaciones.length + ubicaciones.length ));
            AGenApp.getInterfaz().getTxtNavegarGeneraciones().setText(
                            String.valueOf(iteracion - Optimo.generacionesCorteOptimo));
        }
    }

    private void escribirEncabezado() {
         agen.util.log.Log.crearArchivoLog(null);
         agen.util.log.Log.escribirLinea("** Prueba con matriz de " +
                 " dimension:" + matrizMulticriterio.rows);
         agen.util.log.Log.escribirLinea("** Numero de incognitas: "
                 + ubicaciones.length);
         agen.util.log.Log.escribirLinea("** Numero de corridas Batch: "
                 + numCorridasBatch);
         String strMetodoMutacion = "";
         switch (metodoMutacion) {
             case Mutacion.ADAPTATVA_POR_CONVERGENCIA:
                 strMetodoMutacion = "ADAPTATVA_POR_CONVERGENCIA";
                 break;
             case Mutacion.ADAPTATVA_POR_TEMPERATURA_CRESIENTE:
                 strMetodoMutacion = "ADAPTATVA_POR_TEMPERATURA_CRESIENTE";
                 break;
             case Mutacion.ADAPTATVA_POR_TEMPERATURA_DECRESIENTE:
                 strMetodoMutacion = "ADAPTATVA_POR_TEMPERATURA_DECRESIENTE";
                 break;
             case Mutacion.SIMPLE:
                 strMetodoMutacion = "SIMPLE";
                 break;
         }
         agen.util.log.Log.escribirLinea("** Configuracoines AG: \n"
                 + "   Seleccion: " + porcentajeSeleccion + "\n"
                 + "      Elitista: " + porcentajeElitista + "\n"
                 + "      Ranking: " + porcentajeRanking + "\n"
                 + "      Ruleta: " + porcentajeRuleta + "\n"
                 + "   Cruce:" + porcentajeCruce + "\n"
                 + "      Binomial: " + porcentajeBinomial + "\n"
                 + "      Punto: " + porcentajePunto + "\n"
                 + "      MultiPunto: " + porcentajeMultiPunto + "\n"
                 + "   Mutación: " + porcentajeMutacion + "\n"
                 + "      metodo Mutación: " + strMetodoMutacion + "\n"
                 + "      Min Prob Mutación: " + MinPMutacion + "\n"
                 + "      Max Prob Mutación: " + maxPMutacion + "\n"
                 );
         agen.util.log.Log.escribirLinea("Corrida Nº" +"\t"+"Resuelto en Generaciones"
                 +"\t "+ "Mejor Aptitud Primera Gen" + "\t" + "Mejor Aptitud Ultima Gen");

                 
    }

    private void escribirLog() {
        if (correrBatch && numCorridasBatch == AGenApp.batchNumCorridas){
            escribirEncabezado();
        }

        agen.util.log.Log.escribirLinea((AGenApp.batchNumCorridas - numCorridasBatch)
                + "\t" + (iteracion - Optimo.generacionesCorteOptimo) + "\t" +
                AGenApp.historialAptitudes[0][0] + "\t" +
                AGenApp.historialAptitudes[iteracion - 1][0]);

        if (correrBatch && numCorridasBatch == 1){
            agen.util.log.Log.cerrarArchivo();
        }
        
    }

    private void guardarHistorial() {
        byte[][] histPob = AGenApp.historialpoblaciones;
        double[][] histAp = AGenApp.historialAptitudes;
        int[][] histApOrd = AGenApp.historialAptitudesOrdenadas;
        if (histAp.length == iteracion){
            AGenApp.historialAptitudes = Arrays.copyOf(histAp, histAp.length + 100);
        }
        if (histPob.length == iteracion){
            AGenApp.historialpoblaciones = Arrays.copyOf(histPob, histPob.length + 100);
        }
        if (histApOrd.length == iteracion){
            AGenApp.historialAptitudesOrdenadas = Arrays.copyOf(histApOrd,
                    histApOrd.length + 100);;
        }
        AGenApp.historialpoblaciones[iteracion] = Arrays.copyOf(poblacion, poblacion.length);
        AGenApp.historialAptitudes[iteracion] = Arrays.copyOf(aptitudes, aptitudes.length);
        AGenApp.historialAptitudesOrdenadas[iteracion] = Arrays.copyOf(
                aptitudesOrdenadas, aptitudesOrdenadas.length);
     
    }
  
    private int iterar() {
        int offset;
        boolean optimoEncontrado = false;
        byte[] poblacionAux;
        /* Array con los indices de los individuos para saber a que individuo
         * corresponde cada aptitud luego de pasar por sort1
         */
        if (aptitudesOrdenadas == null) {
            aptitudesOrdenadas = new int[poblacion.length / ubicaciones.length];
        }
        
        try {
            while (!optimoEncontrado && !AGenApp.pause) {
                iteracion++;
                offset = 0;
                aptitudes = Aptitud.calcularAptitudJblas(poblacion, 
                        AGenApp.prioridades, ubicaciones, matrizMulticriterio,
                        aptitudesOrdenadas, iteracion);

                indiceAptitudOptima = Optimo.encontrarOptimo(aptitudes,
                        aptitudesOrdenadas);
                output();
                if (indiceAptitudOptima != -1) {
                    optimoEncontrado = true;
                    break;
                }
                MetodoSeleccionCombinado[] metodosSeleccion = armarSeleccion(
                        porcentajeElitista, porcentajeRanking, porcentajeRuleta);
                offset = Seleccion.seleccion(offset, porcentajeSeleccion,
                        poblacion, poblacionModificada, aptitudes, metodosSeleccion,
                        ubicaciones.length, aptitudesOrdenadas);
                if (iteracion == 0){
                    offsets[0] = offset;
                }
                MetodoCruceCombinado[] metodosCruce = armarCruce(porcentajeBinomial,
                        porcentajePunto, porcentajeMultiPunto, multiPunto);
                offset = Cruce.cruce(offset, porcentajeCruce, poblacion,
                        poblacionModificada, aptitudes, metodosCruce,
                        ubicaciones.length, aptitudesOrdenadas);
                if (iteracion == 0){
                    offsets[1] = offset;
                }

                offset = Mutacion.mutacion(offset, porcentajeMutacion, poblacion,
                        poblacionModificada, aptitudes, metodoMutacion,
                        ubicaciones.length);
                if (iteracion == 0){
                    offsets[2] = offset;
                }
                //No hace falta "cerear" poblacionModificada porque se
                //sobreescribe en los operadores.
                guardarHistorial();
                poblacionAux = poblacion;
                poblacion = poblacionModificada;
                poblacionModificada = poblacionAux;

                if (iteracion == corteIteraciones){
                    AGenApp.pause = true;
                }
            }
        } catch (PorcentajesInconsistentes ex) {
            Logger.getLogger(AGenApp.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MetodoInexistente ex) {
            Logger.getLogger(AGenApp.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(AGenApp.class.getName()).log(Level.SEVERE, null, ex);
        }
        return indiceAptitudOptima;
    }
    private void output(){
        if (indiceAptitudOptima != -1) {
            System.out.println("El optimo es: " + aptitudesOrdenadas[
                    indiceAptitudOptima]);
            int indice = aptitudesOrdenadas[indiceAptitudOptima] *
                    ubicaciones.length;
            for (int i = 0; i < ubicaciones.length; i++) {
                System.err.print(AGenApp.prioridades[poblacion[indice + i]]
                        + ", ");
            }
            System.out.print(aptitudes[aptitudes.length - 1] + ",||, ");
            System.out.println(aptitudes[0] + ", Iteracion: " + iteracion);
        }
        if (iteracion % 10 == 0) {
            System.out.print(aptitudes[aptitudes.length - 1] + ",||, ");
            System.out.println(aptitudes[0] + ", Iteracion: " + iteracion);
        }
    }
    /**
     * Genera los parametros para la clase Seleccion en funcion de los valores de
     * los porcentajes.
     * @param porcentajeElitista
     * @param porcentajeRanking
     * @param porcentajeRuleta
     * @return
     */
    private static MetodoSeleccionCombinado[] armarSeleccion(int porcentajeElitista,
            int porcentajeRanking, int porcentajeRuleta) {
        Vector<MetodoSeleccionCombinado> salida = new Vector<MetodoSeleccionCombinado>();
        if (porcentajeElitista != 0) {
            MetodoSeleccionCombinado elitista = new MetodoSeleccionCombinado(
                    Seleccion.METODO_ELITISTA, porcentajeElitista);
            salida.add(elitista);
        }
        if (porcentajeRanking != 0) {
            MetodoSeleccionCombinado ranking = new MetodoSeleccionCombinado(
                    Seleccion.METODO_RANKING, porcentajeRanking);
            salida.add(ranking);
        }
        if (porcentajeRuleta != 0) {
            MetodoSeleccionCombinado ruleta = new MetodoSeleccionCombinado(
                    Seleccion.METODO_RULETA, porcentajeRuleta);
            salida.add(ruleta);
        }
        return salida.toArray(new MetodoSeleccionCombinado[]{});
    //return (MetodoSeleccionCombinado[]) salida.toArray();
    }
    /**
     * Genera los parametros para la clase Cruce en funcion de los valores de
     * los porcentajes.
     * @param porcentajeBinomial
     * @param porcentajePunto
     * @param porcentajeMultiPunto
     * @param multiPuntos
     * @return
     */
    private static MetodoCruceCombinado[] armarCruce(int porcentajeBinomial,
            int porcentajePunto, int porcentajeMultiPunto, int[] multiPuntos) {
        Vector<MetodoCruceCombinado> salida = new Vector<MetodoCruceCombinado>();
        if (porcentajeBinomial != 0) {
            MetodoCruceCombinado binomial = new MetodoCruceCombinado(
                    Cruce.METODO_BINOMIAL, porcentajeBinomial);
            salida.add(binomial);
        }
        if (porcentajePunto != 0) {
            MetodoCruceCombinado punto = new MetodoCruceCombinado(
                    Cruce.METODO_PUNTO, porcentajePunto, multiPuntos);
            salida.add(punto);
        }
        if (porcentajeMultiPunto != 0) {
            MetodoCruceCombinado multiPunto = new MetodoCruceCombinado(
                    Cruce.METODO_MULTI_PUNTO, porcentajeMultiPunto, multiPuntos);
            salida.add(multiPunto);
        }
        return salida.toArray(new MetodoCruceCombinado[]{});
    //return (MetodoCruceCombinado[]) salida.toArray();
    }
    /**
     * Calcula el ratio de consistencia CI / R; CI = aptitud[] / OrdenMatriz - 1
     * @return
     */
    private double cr() {
        if (matrizMulticriterio.rows == 2) {
            return Double.POSITIVE_INFINITY;
        } else {
            return (aptitudes[0] / ((double) matrizMulticriterio.rows - 1)) /
                    riList[matrizMulticriterio.rows - 2];
        }
    }
    /*Valores de tabla para el indice de consistencia promedio de matrices de
     * dimension 2 a 10 completadas con valores aleatorios*/
    static final double[] riList = {0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45, 1.51};
}
